﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Text;
using System.Collections;
using System.Web.UI.WebControls;
using System.Security.Cryptography;
using System.Text.RegularExpressions;
using System.Globalization;

/// <summary>
/// Miscellaneuous methods
/// </summary>
public class Utils {
    private static Random seed = new Random();
    /// <summary>
    /// Joins a list using a separator.
    /// </summary>
    /// <param name="list">The list to join.</param>
    /// <param name="separator">The separator to use.</param>
    /// <param name="trim">If set to <c>true</c>, trims whitespace from each index.</param>
    /// <returns>A string containing the values separated by the separator</returns>
    public static string Implode(ICollection<string> list, string separator, bool trim) {
        return Implode(list, separator, trim, null);
    }

    /// <summary>
    /// Joins a list using a separator.
    /// </summary>
    /// <param name="list">The list to join.</param>
    /// <param name="separator">The separator to use.</param>
    /// <param name="trim">If set to <c>true</c>, trims whitespace from each index.</param>
    /// <param name="useAnd">The separator to use to join the second to last and last item, or <c>null</c> to use the separator. Eg: Could be "and".</param>
    /// <returns>A string containing the values separated by the separator</returns>
    public static string Implode(ICollection<string> list, string separator, bool trim, string lastSeparator) {
        StringBuilder buffer = new StringBuilder();
        int num = list.Count,
            added = 0;
        foreach(string item in list) {
            if(trim) {
                buffer.Append(item.Trim());
            } else {
                buffer.Append(item);
            }
            if(added + 1 < num) {
                if(added == num - 2 && lastSeparator != null) {
                    buffer.Append(lastSeparator);
                } else {
                    buffer.Append(separator);
                }
            }
            added++;
        }
        return buffer.ToString();
    }

    /// <summary>
    /// Generates a (hopefully) unique ID using a combination of 53 (non-HTML safe) or 39 (HTML safe) characters.
    /// There are 1.74e17 combinations if the ID does not need to be HTML safe, otherwise there are just over 3500 million
    /// </summary>
    /// <param name="HTMLSafe"><c>true</c> if the generated ID must be valid in HTML without encoding.</param>
    /// <returns>The generated ID</returns>
    public static string GenID(bool HTMLSafe) {
        StringBuilder bld = new StringBuilder();
        int len = 6;
        if(HTMLSafe) {
            string possible = "ABCEDFGHIJKLMNOPQRSTUVWXYZ0123456789~_-",
                   firstPossible = "ABCEDFGHIJKLMNOPQRSTUVWXYZ"; // make sure the first character isn't a number, which invalidates xhtml
            bld.Append(possible[seed.Next(firstPossible.Length - 1)]);
            for(int i = 1; i < len; i++) {
                bld.Append(possible[seed.Next(possible.Length - 1)]);
            }
        } else {
            string possible = "ABCEDFGHIJKLMNOPQRSTUVWXYZ0123456789%£$!*/#~][{}|=_-^";
            for(int i = 0; i < len; i++) {
                bld.Append(possible[seed.Next(possible.Length - 1)]);
            }
        }
        return bld.ToString();
    }

    /// <summary>
    /// Calculates the MD5 hash of the specified text
    /// </summary>
    /// <param name="text">The text.</param>
    /// <returns>The MD5 hash.</returns>
    public static string MD5(string text) {
        MD5 md5 = new MD5CryptoServiceProvider();
        byte[] originalBytes = ASCIIEncoding.UTF8.GetBytes(text);
        byte[] encodedBytes = md5.ComputeHash(originalBytes);
        return BitConverter.ToString(encodedBytes).Replace("-", "").ToLower();
    }

    /// <summary>
    /// Applies salt to a password.
    /// </summary>
    /// <param name="password">The password.</param>
    /// <returns></returns>
    public static string SaltPassword(string password) {
        return SaltPassword(password, System.Web.Configuration.WebConfigurationManager.AppSettings["salt"]);
    }
    /// <summary>
    /// Applies salt to a password.
    /// </summary>
    /// <param name="password">The password.</param>
    /// <param name="salt">The salt to use.</param>
    /// <returns></returns>
    public static string SaltPassword(string password, string salt) {
        return MD5(salt + password);
    }

    /// <summary>
    /// Encodes a string to make it HTML compliant, replacing extended characters with their entities.
    /// This is not needed by ASP.NET.
    /// </summary>
    /// <param name="text">The text to encode.</param>
    /// <returns>An HTML encoded string</returns>
    public static string HtmlEncode(string text) {
        char[] chars = HttpUtility.HtmlEncode(text).ToCharArray();
        StringBuilder result = new StringBuilder((int)(text.Length * 1.1));
        foreach(char c in chars) {
            int value = Convert.ToInt32(c);
            if(value > 127) {
                result.AppendFormat("&#{0};", value);
            } else {
                result.Append(c);
            }
        }
        return result.ToString();
    }

    /// <summary>
    /// Decode an HTML encoded string (including entities).
    /// This is not needed by ASP.NET.
    /// </summary>
    /// <param name="text">The text to decode.</param>
    /// <returns>A decoded string</returns>
    public static string HtmlDecode(string text) {
        char[] chars = HttpUtility.HtmlDecode(text).ToCharArray();
        return new string(chars);
    }

    /// <summary>
    /// Converts a .NET compatible country code to a Java compatible country code. These are mostly the same apart from a couple of unique cases.
    /// </summary>
    /// <param name="dotNetCulture">The dot net culture.</param>
    /// <returns></returns>
    public static string DotNetCulture2JavaCulture(string dotNetCulture) {
        string javaCulture = string.Empty;
        switch(dotNetCulture) {
            case "he-IL":
                javaCulture = "iw_IL"; // bug as of Java v1.6.7
                break;
            case "zh-Hans":
			case "zh-CHS":
				javaCulture = "zh_CN";
				break;
			case "zh-Hant":
			case "zh-CHT":
                javaCulture = "zh_TW";
                break;
            default:
                javaCulture = dotNetCulture.Replace('-', '_');
                break;
        }
        return javaCulture;
    }

    /// <summary>
    /// Converts a Java compatible country code to a .NET compatible country code. These are mostly the same apart from a couple of unique cases.
    /// </summary>
    /// <param name="dotNetCulture">The dot net culture.</param>
    /// <returns></returns>
    public static string JavaCulture2DotNetCulture(string javaCulture) {
        string dotNetCulture = string.Empty;
        switch(javaCulture) {
            case "iw_IL":
                dotNetCulture = "he-IL"; // bug as of Java v1.6.7
                break;
            default:
                dotNetCulture = javaCulture.Replace('_', '-');
                break;
        }
        return dotNetCulture;
    }

    /// <summary>
    /// For the specified string, replaces each Regex in pattern[], with it's equivalent index of replacements[]
    /// </summary>
    /// <param name="input">The input.</param>
    /// <param name="pattern">The pattern.</param>
    /// <param name="replacements">The replacements.</param>
    /// <returns></returns>
    public static string PregReplace(string stringToReplace, string[] pattern, string[] replacements, RegexOptions options) {
        if(replacements.Length != pattern.Length) {
            throw new ArgumentException("Pattern and replacement arrays must be equal length");
        }
        for(int i = 0; i < pattern.Length; i++) {
            stringToReplace = Regex.Replace(stringToReplace, pattern[i], replacements[i], options);
        }
        return stringToReplace;
    }

    /// <summary>
    /// Replaces the template tags in the specified template with their values from the source and destination arrays provided.
    /// </summary>
    /// <param name="template">The template.</param>
    /// <param name="pattern">The array of template tags.</param>
    /// <param name="replacements">The array of replacements.</param>
    /// <returns>A formatted template tag</returns>
    public static string FormatTemplateTags(string template, string[] pattern, string[] replacements) {
        if(replacements.Length != pattern.Length) {
            throw new ArgumentException("Pattern and replacement arrays must be equal length");
        }
        if(template.IndexOf("{=") < 0) {
            return template;
        } else {
            StringBuilder buffer = new StringBuilder(template);
            for(int i = 0; i < pattern.Length; i++) {
                buffer.Replace(pattern[i], replacements[i]);
            }
            return buffer.ToString();
        }
    }

    /// <summary>
    /// Gets a message with instructions for the user to copy and paste sections into the web.config when ASP.NET cannot write to it automatically.
    /// </summary>
    /// <param name="salt">The salt.</param>
    /// <returns></returns>
    public static string GetWebConfig(Dictionary<string, string> appSettings, string connectionString, string providerName) {
        StringBuilder buffer = new StringBuilder(Resources.Errors.ManualWebConfig);
        buffer.Append("</p><p><strong>");
        if(appSettings != null) {
            buffer.Append("&lt;appSettings&gt;<br />&lt;clear /&gt;<br />");
            string fmt = "&lt;add key=&quot;{0}&quot; value=&quot;{1}&quot; /&gt;<br />";
            foreach(KeyValuePair<string, string> setting in appSettings) {
                buffer.AppendFormat(fmt, setting.Key, setting.Value);
            }
            buffer.Append("&lt;/appSettings&gt;<br />");
        }
        if(connectionString != null) {
            buffer.Append("&lt;connectionStrings&gt;<br />&lt;clear /&gt;<br />");

            buffer.Append(string.Format("&lt;add name=&quot;Default&quot; connectionString=&quot;{0}&quot; providerName=&quot;{1}&quot; /&gt;<br />", connectionString, providerName));
            buffer.Append("&lt;/connectionStrings&gt;");
        }
        buffer.Append("</strong>");
        return buffer.ToString();
    }

    /// <summary>
    /// Trims a string to the specified length, adding an ellipsis (...) if it is longer than the required length.
    /// </summary>
    /// <param name="text">The text to trim.</param>
    /// <param name="length">The maximum length of the final string.</param>
    /// <returns>A trimmed version of the text with an ellipsis if it was too long</returns>
    public static string TrimText(string text, int length) {
        if(text == null) {
            return "";
        }
        string snippet = Regex.Replace(text, @"<(.|\n)*?>", string.Empty); // strip HTML tags
        if(snippet.Length > length) {
            int stringLen = length - 3;
            if(stringLen < 0) {
                stringLen = 0;
            }
            return snippet.Substring(0, stringLen).Trim() + "...";
        } else {
            return snippet;
        }
    }

    /// <summary>
    /// Assigns a jQuery click/ajax listener to a 'Find Similar' button.
    /// </summary>
    /// <param name="buffer">The buffer containing the rest of the JavaScript.</param>
    /// <param name="buttonId">The client ID of the 'find translaiton' button.</param>
    /// <param name="textboxId">The client ID of the textbox containing the search string.</param>
    /// <param name="containerId">The client ID of the container for the results.</param>
    /// <param name="languageCode">The language code of the translation.</param>
    /// <param name="addFormResource"><c>true</c> if this button is on the 'add resource' form.</param>
    public static void AssignFindSimilarListener(StringBuilder buffer, string buttonId, string textboxId, string containerId, string languageCode, bool addFormResource) {
        buffer.AppendFormat("addFindSimilarListener('{0}', '{1}', '{2}', '{3}', {4});\n", buttonId, textboxId, containerId, languageCode, addFormResource ? "true" : "false");
    }

    /// <summary>
    /// Assigns a jQuery click/ajax listener to a 'Translate with Google' button.
    /// </summary>
    /// <param name="buffer">The buffer containing the rest of the JavaScript.</param>
    /// <param name="buttonId">The client ID of the 'find translaiton' button.</param>
    /// <param name="srcTextboxId">The client ID of the textbox containing the string to translate.</param>
    /// <param name="srcGoogleCode">The Google code of the source textbox.</param>
    /// <param name="destTextboxId">The client ID of the textbox in which to display the translation.</param>
    /// <param name="destGoogleCode">The Google code of the destination textbox.</param>
    public static void AssignGoogleTranslateSimilarListener(StringBuilder buffer, string buttonId, string srcTextboxId, string srcGoogleCode, string destTextboxId, string destGoogleCode) {
        buffer.AppendFormat("addGoogleTranslateListener('{0}', '{1}', '{2}', '{3}', '{4}');\n", buttonId, srcTextboxId, srcGoogleCode, destTextboxId, destGoogleCode);
    }

    /// <summary>
    /// Assigns a jQuery click listener to open the PO editor.
    /// </summary>
    /// <param name="buffer">The buffer containing the rest of the JavaScript.</param>
    /// <param name="buttonId">The ID of the button to which to assign the listener.</param>
    /// <param name="dataTextboxId">The ID of the textbox containing the PO metadata.</param>
    public static void AssignEditPOListener(StringBuilder buffer, string buttonId, string dataTextboxId) {
        buffer.AppendFormat("$('#{0}').click(function() {{ editPO('{1}'); return false; }});\n", buttonId, dataTextboxId);
    }

    /// <summary>
    /// Gets the CultureInfo for a given language code. If the culture does not exist, creates it for future use first.
    /// </summary>
    /// <param name="cultureCode">The culture name (code).</param>
    /// <returns>A CultureInfo representing the culture</returns>
    public static CultureInfo GetCultureInfo(string cultureCode) {
        if(cultureCode.Length < 2) {
            return null;
        }
        CultureInfo info;
        try {
            info = CultureInfo.GetCultureInfo(cultureCode);
        } catch {
            CultureAndRegionInfoBuilder builder = new CultureAndRegionInfoBuilder(cultureCode, CultureAndRegionModifiers.None);
            builder.LoadDataFromCultureInfo(CultureInfo.GetCultureInfo("en-GB"));
            builder.LoadDataFromRegionInfo(new RegionInfo("en-GB"));
            builder.TwoLetterISOLanguageName = cultureCode.Substring(0, 2);
            builder.TwoLetterISORegionName = "unknown";
            builder.CultureNativeName = cultureCode;
            builder.CultureEnglishName = cultureCode;
            builder.Register();
            info = CultureInfo.GetCultureInfo(cultureCode);
        }
        return info;
    }

    /// <summary>
    /// Gets the English name for the specified culture code, or returns the code if the culture is not available.
    /// </summary>
    /// <param name="cultureCode">The culture code.</param>
    /// <returns>The English name for the specified culture code, or returns the code if the culture is not available</returns>
    public static string GetSafeEnglishName(string cultureCode) {
        try {
            return CultureInfo.GetCultureInfo(cultureCode).EnglishName;
        } catch {
            return cultureCode;
        }
    }

    /// <summary>
    /// Gets the ISO 3166 code of the specified culture.
    /// </summary>
    /// <param name="info">The CultureInfo representing the culture.</param>
    /// <returns>An ISO3166 code to reresent a country which uses the CultureInfo specified</returns>
    public static string GetISO3166(CultureInfo info) {
        if(info.IsNeutralCulture) {
            if(info.TwoLetterISOLanguageName == "zh") { // chinese is awkward
                return "cn";
            } else {
                return new RegionInfo(CultureInfo.CreateSpecificCulture(info.Name).LCID).TwoLetterISORegionName;
            }
        } else {
            return new RegionInfo(info.Name).TwoLetterISORegionName;
        }
    }

    /// <summary>
    /// Equivalent to Java's native2ascii utility.
    /// Replaces all characters with code points greater than 127 (0x007C) to unicode escape sequences (\uXXXX)
    /// </summary>
    /// <param name="text">The text.</param>
    /// <returns>The text with unicode escape sequences instead of codepoints >127</returns>
    public static string UnicodeEscape(string text) {
        StringBuilder sb = new StringBuilder(text.Length + 80);
        for(int i = 0; i < text.Length; i++) {
            char c = text[i];
            if(c <= 0x7F) { // 127
                sb.Append(c);
            } else {
                sb.Append("\\u" + Convert.ToString((int)c, 16).PadLeft(4, '0'));
            }
        }
        return sb.ToString();
    }

    /// <summary>
    /// Retrieves a Google translate code for a specified ISO 639-1 code. This is usually the same but sometimes varies. An empty string is returned if the code is unsupported by Google Translate.
    /// </summary>
    /// <param name="isoCode">The ISO 639-1 code.</param>
    /// <returns>The equivalent Google Translate language code, or an empty string if the code is unsupported by Google Translate.</returns>
    public static string ISOCode2GoogleCode(string isoCode) {
        string googleCode = string.Empty;
        switch(isoCode) { //some .NET peculiarities
            case "he":
                googleCode = "iw";
                break;
            case "pt":
                googleCode = "pt-PT";
                break;
            case "zh-CHS":
                googleCode = "zh-CN";
                break;
            case "zh-CHT":
                googleCode = "zh-TW";
                break;
            default: // only assign the code if the language is supported (null = hide the Google button)
                if(Constants.GoogleSupportedLanguages.ContainsKey(isoCode)) {
                    googleCode = isoCode;
                }
                break;
        }
        return googleCode;
    }

    /// <summary>
    /// Formats a localized string containing {linkstart} and {linkend} tags, then returns a hyperlink to <c>linkURL</c>.
    /// </summary>
    /// <param name="format">The string format.</param>
    /// <param name="linkUrl">The link URL.</param>
    /// <returns>The localized string containing a link</returns>
    public static string FormatLink(string format, string linkUrl) {
        if(string.IsNullOrEmpty(format) || string.IsNullOrEmpty(linkUrl)) {
            return string.Empty;
        }
        return format.Replace("{linkstart}", string.Format("<a href=\"{0}\">", linkUrl)).Replace("{linkend}", "</a>");
    }

    /*public static string GetString(string bundle, string key) {
        return HttpContext.GetGlobalResourceObject(bundle, key, CultureInfo.CurrentUICulture).ToString();
    }*/

    /// <summary>
    /// Sanitizes a filename by removing invalid characters.
    /// </summary>
    /// <param name="path">The path.</param>
    /// <returns>The filename with invalid characters stripped.</returns>
    public static string SanitizeFilename(string path) {
        if(path == null || path.Length == 0) {
            return path;
        }
        string safePath = path;
        Array.ForEach(System.IO.Path.GetInvalidFileNameChars(), c => safePath = safePath.Replace(c.ToString(), string.Empty));
        return safePath;
    }
}
